using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace _3D_Game
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class ModelManager : DrawableGameComponent
    {
        // List of models
        List<BasicModel> models = new List<BasicModel>();
        List<BasicModel> PowerUp = new List<BasicModel>();
        List<BasicModel> Health = new List<BasicModel>();
        public BasicModel player;
        
        public Vector3 playerPosition;
        Vector3 playerDirection;
        
        //Enemy position and direction
        Vector3 E_position;
        Vector3 E_direction;

        // Spawn variables
        Vector3 maxSpawnLocation = new Vector3(100, 100, -2000);
        int nextSpawnTime = 0;
        int timeSinceLastSpawn = 0;
        float maxRollAngle = MathHelper.Pi / 40;

        // Enemy count
        //int enemiesThisLevel = 0;
        int totalEnemyKilled = 0;
        public int enemyKilled = 0;
       

        // Current level
        public int currentLevel = 0;

        //player health
      //  int life = 200;

        // List of LevelInfo objects
        public List<LevelInfo> levelInfoList = new List<LevelInfo>();

        // Shot stuff
        List<BasicModel> shots = new List<BasicModel>();
        float shotMinZ = -2000;

        List<BasicModel> E_shots = new List<BasicModel>();
      
        //Explosion stuff
        List<ParticleExplosion> explosions = new List<ParticleExplosion>();
        ParticleExplosionSettings particleExplosionSettings = new ParticleExplosionSettings();
        ParticleSettings particleSettings = new ParticleSettings();
        Texture2D explosionTexture;
        Texture2D explosionColorsTexture;
        Effect explosionEffect;

        // Star sheet stuff
        ParticleStarSheet stars;
        Effect starEffect;
        Texture2D starTexture;

        // Scoring
        const int pointsPerKill = 20;
        const int bloodPerDamage = 5;


       
        public ModelManager(Game game)
            : base(game)
        {
            // Initialize game levels
            levelInfoList.Add(new LevelInfo(1000, 3000, 30, 2, 6, 10));
            levelInfoList.Add(new LevelInfo(900, 2800, 40, 2, 6, 15));
            levelInfoList.Add(new LevelInfo(800, 2600, 50, 2, 6, 20));
            levelInfoList.Add(new LevelInfo(700, 2400, 60, 3, 7, 25));
            levelInfoList.Add(new LevelInfo(600, 2200, 65, 3, 7, 30));
            levelInfoList.Add(new LevelInfo(500, 2000, 65, 3, 7, 30));
            levelInfoList.Add(new LevelInfo(400, 1800, 65, 4, 7, 30));
            levelInfoList.Add(new LevelInfo(300, 1600, 65, 4, 8, 30));
            levelInfoList.Add(new LevelInfo(200, 1400, 65, 5, 8, 35));
          
        }

        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            // Set initial spawn time
            SetNextSpawnTime();
            Reset();

            base.Initialize();
        }

        protected void Reset()
        {
            playerPosition = (new Vector3(0, 0, 0));
            playerDirection = (((Game1)Game).camera.GetCameraDirection);
           
            /*for (int i = 0; i < shots.Count; i++)
                shots.RemoveAt(i);
            for (int i = 0; i < explosions.Count; i++)
                explosions.RemoveAt(i);
            for (int i = 0; i < models.Count; i++)
                models.RemoveAt(i);*/
        }

        protected override void LoadContent()
        {
            // Load explosion textures and effect
            explosionTexture = Game.Content.Load<Texture2D>(@"Textures\Particle");
            explosionColorsTexture = Game.Content.Load<Texture2D>(@"Textures\ParticleColors");
            explosionEffect = Game.Content.Load<Effect>(@"effects\particle");

            // Set effect parameters that don't change per particle
            explosionEffect.CurrentTechnique = explosionEffect.Techniques["Technique1"];
            explosionEffect.Parameters["theTexture"].SetValue(explosionTexture);

            player = new BasicModel(Game.Content.Load<Model>(@"models\Ship"), playerPosition, playerDirection);

            // Load star texture and effect
            starTexture = Game.Content.Load<Texture2D>(@"textures\stars");
            starEffect = explosionEffect.Clone();
            starEffect.CurrentTechnique = starEffect.Techniques["Technique1"];
            starEffect.Parameters["theTexture"].SetValue(explosionTexture);

            // Initialize particle star sheet
            stars = new ParticleStarSheet(
                GraphicsDevice,
                new Vector3(2000, 2000, -1500),
                1500, starTexture,
                particleSettings,
                starEffect);

            base.LoadContent();
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            // Check to see if it's time to spawn
            CheckToSpawnEnemy(gameTime);

           
            UpdatePlayer();

            UpdateModels();
           
            UpdateShots();

            UpdateEShots();

            UpdatePowerUp();

            UpdateHealth();

            UpdateExplosions(gameTime);

            base.Update(gameTime);
        }

        protected void UpdateHealth()
        {
            // Loop through all models and call Update
            for (int i = 0; i < Health.Count; ++i)
            {
                // Update each model
                Health[i].Update();

                // Remove models that are out of bounds
                if (Health[i].GetWorld().Translation.Z > player.GetWorld().Translation.Z + 100)
                {
                    Health.RemoveAt(i);
                    --i;
                }
            }
        }

        protected void UpdatePowerUp()
        {
            // Loop through all models and call Update
            for (int i = 0; i < PowerUp.Count; ++i)
            {
                // Update each model
                PowerUp[i].Update();

                // Remove models that are out of bounds
                if (PowerUp[i].GetWorld().Translation.Z > player.GetWorld().Translation.Z + 100 )
                {
                    PowerUp.RemoveAt(i);
                    --i;
                }
            }
        }

        protected void UpdateModels()
        {
            // Loop through all models and call Update
            for (int i = 0; i < models.Count; ++i)
            {
                // Update each model
                models[i].Update();

                // Remove models that are out of bounds
                if (models[i].GetWorld().Translation.Z > player.GetWorld().Translation.Z + 100 )
                {
                    models.RemoveAt(i);
                    --i;

                    // Reset the kill count
                    //consecutiveKills = 0;

                   
                }
            }
        }

        public override void Draw(GameTime gameTime)
        {
            // Loop through and draw each model

            foreach (BasicModel bm in models)
            {
                bm.Draw(((Game1)Game).camera);
            }

            // Loop through and draw each shot
            foreach (BasicModel bm in shots)
            {
                bm.Draw(((Game1)Game).camera);
            }

            foreach (BasicModel bm in PowerUp)
            {
                bm.Draw(((Game1)Game).camera);
            }

            foreach (BasicModel bm in Health)
            {
                bm.Draw(((Game1)Game).camera);
            }

            foreach (BasicModel bm in E_shots)
            {
                bm.Draw(((Game1)Game).camera);
            }

            // Loop through and draw each particle explosion
            foreach (ParticleExplosion pe in explosions)
            {
                pe.Draw(((Game1)Game).camera);
            }

            // Draw the star sheet
            stars.Draw(((Game1)Game).camera);

            player.Draw(((Game1)Game).camera);

            base.Draw(gameTime);
        }

        private void SetNextSpawnTime()
        {
            // Reset the variables to indicate the next enemy spawn time
            nextSpawnTime = ((Game1)Game).rnd.Next(
                levelInfoList[currentLevel].minSpawnTime,
                levelInfoList[currentLevel].maxSpawnTime);
            timeSinceLastSpawn = 0;
        }

        protected void CheckToSpawnEnemy(GameTime gameTime)
        {
          
                timeSinceLastSpawn += gameTime.ElapsedGameTime.Milliseconds;
                if (timeSinceLastSpawn > nextSpawnTime)
                {
                    SpawnEnemy();

                }
        }


        private void SpawnEnemy()
        {
            // Generate random position with random X and random Y
            // between -maxX and maxX and -maxY and maxY. Z is always
            // the same for all ships.

            E_position = new Vector3(((Game1)Game).rnd.Next( /////////////////////////////////////////////////////////
                -(int)maxSpawnLocation.X, (int)maxSpawnLocation.X),
                ((Game1)Game).rnd.Next(
                -(int)maxSpawnLocation.Y, (int)maxSpawnLocation.Y),
                maxSpawnLocation.Z);

            // Direction will always be (0, 0, Z), where
            // Z is a random value between minSpeed and maxSpeed
            E_direction = new Vector3(0, 0,
                 ((Game1)Game).rnd.Next(
                (int)levelInfoList[currentLevel].minSpeed, (int)levelInfoList[currentLevel].maxSpeed));
                

            // Get a random roll rotation between -maxRollAngle and maxRollAngle
            float rollRotation = (float)((Game1)Game).rnd.NextDouble() *
                    maxRollAngle - (maxRollAngle / 2);

            // Add model to the list
            models.Add(new SpinningEnemy(
                Game.Content.Load<Model>(@"models\spaceship"),
                E_position, E_direction, 0, 0, rollRotation));

          
            SetNextSpawnTime();

        }

        public void AddShot(Vector3 position, Vector3 direction)
        {
            shots.Add(new SpinningEnemy(
                Game.Content.Load<Model>(@"models\ball"),
                position, direction, 0, 0, 0));
        }

        public void SpawnPowerUp()
        {
            float powerUpSpeed = 3.0f;
            // Generate random position with random X and random Y
            // between -maxX and maxX and -maxY and maxY. Z is always
            // the same for all powerups.
            Vector3 P_position = new Vector3(((Game1)Game).rnd.Next( /////////////////////////////////////////////////////////
                -(int)maxSpawnLocation.X, (int)maxSpawnLocation.X),
                ((Game1)Game).rnd.Next(
                -(int)maxSpawnLocation.Y, (int)maxSpawnLocation.Y),
                maxSpawnLocation.Z);

            // Direction will always be (0, 0, Z)
            
            Vector3 P_direction = E_direction * powerUpSpeed;

           
            // Get a random roll rotation between -maxRollAngle and maxRollAngle
            float rollRotation = (float)((Game1)Game).rnd.NextDouble() *
                    maxRollAngle - (maxRollAngle / 2);

            // Add model to the list
            PowerUp.Add(new SpinningEnemy(
                Game.Content.Load<Model>(@"models\Ship"),
                E_position, E_direction*powerUpSpeed, 0, 0, rollRotation));
            SetNextSpawnTime();
        }

        public void SpawnHealth()
        {
            float powerUpSpeed = 3.0f;
           
            float rollRotation = (float)((Game1)Game).rnd.NextDouble() *
                    maxRollAngle - (maxRollAngle / 2);

            // Add model to the list
            Health.Add(new SpinningEnemy(
                Game.Content.Load<Model>(@"models\ammo"),
                E_position, E_direction*powerUpSpeed, 0, 0, rollRotation));
            SetNextSpawnTime();
        }

        public void UpdatePlayer()
        {
            // Loop through each ModelMesh in both objects and compare
            // all bounding spheres for collisions
            for (int i = 0; i < models.Count; ++i)
            {
                if (models[i].CollidesWith(player.model,
                            player.GetWorld()))
                {
                    // Collision! add an explosion.
                    explosions.Add(new ParticleExplosion(GraphicsDevice,
                        models[i].GetWorld().Translation,
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minLife,
                            particleExplosionSettings.maxLife),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minRoundTime,
                            particleExplosionSettings.maxRoundTime),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minParticlesPerRound,
                            particleExplosionSettings.maxParticlesPerRound),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minParticles,
                            particleExplosionSettings.maxParticles),
                        explosionColorsTexture, particleSettings,
                        explosionEffect));

                    // Update score and HP
                    ((Game1)Game).AddPoints(pointsPerKill * (currentLevel + 1));
                    ((Game1)Game).ReduceBlood(bloodPerDamage * (currentLevel + 1));
                 
                    if (((Game1)Game).life <= 0)
                    {
                        explosions.Add(new ParticleExplosion(GraphicsDevice,
                        player.GetWorld().Translation,
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minLife,
                            particleExplosionSettings.maxLife),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minRoundTime,
                            particleExplosionSettings.maxRoundTime),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minParticlesPerRound,
                            particleExplosionSettings.maxParticlesPerRound),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minParticles,
                            particleExplosionSettings.maxParticles),
                        explosionColorsTexture, particleSettings,
                        explosionEffect));
                        ((Game1)Game).PlayCue("Explosions");
                        
                        ((Game1)Game).ChangeGameState(
                            Game1.GameState.END, currentLevel);
                    }
                   
                    models.RemoveAt(i);
                    ((Game1)Game).PlayCue("Explosions");
                    ++enemyKilled;
                    ++totalEnemyKilled;

                    if (enemyKilled >= levelInfoList[currentLevel].missesAllowed)
                    {
                        // Reset();
                        ++currentLevel;

                        if (currentLevel == 9)
                        {
                            ((Game1)Game).ChangeGameState(Game1.GameState.WIN,currentLevel);
                        }
                        else
                            ((Game1)Game).ChangeGameState(
                               Game1.GameState.LEVEL_CHANGE,
                               currentLevel);
                        enemyKilled = 0;
                    }
                 
             
                    if (totalEnemyKilled % 7 == 0)
                    {
                       
                        SpawnPowerUp();
                    }

                    if (totalEnemyKilled % 10 == 0)
                    {
                        SpawnHealth();
                    }

                }
            }

            for (int j = 0; j < E_shots.Count; ++j)
            {
                // Update each model
                if (player.CollidesWith(E_shots[j].model, E_shots[j].GetWorld()))
                {
                    explosions.Add(new ParticleExplosion(GraphicsDevice,
                                 E_shots[j].GetWorld().Translation,
                                 ((Game1)Game).rnd.Next(
                                     particleExplosionSettings.minLife,
                                     particleExplosionSettings.maxLife),
                                 ((Game1)Game).rnd.Next(
                                     particleExplosionSettings.minRoundTime,
                                     particleExplosionSettings.maxRoundTime),
                                 ((Game1)Game).rnd.Next(
                                     particleExplosionSettings.minParticlesPerRound,
                                     particleExplosionSettings.maxParticlesPerRound),
                                 ((Game1)Game).rnd.Next(
                                     particleExplosionSettings.minParticles,
                                     particleExplosionSettings.maxParticles),
                                 explosionColorsTexture, particleSettings,
                                 explosionEffect));

                    
                    //Update Health
                    ((Game1)Game).ReduceBlood(bloodPerDamage * (currentLevel + 1));
                   
                    if (((Game1)Game).life <= 0)
                    {
                        explosions.Add(new ParticleExplosion(GraphicsDevice,
                        player.GetWorld().Translation,
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minLife,
                            particleExplosionSettings.maxLife),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minRoundTime,
                            particleExplosionSettings.maxRoundTime),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minParticlesPerRound,
                            particleExplosionSettings.maxParticlesPerRound),
                        ((Game1)Game).rnd.Next(
                            particleExplosionSettings.minParticles,
                            particleExplosionSettings.maxParticles),
                        explosionColorsTexture, particleSettings,
                        explosionEffect));
                        ((Game1)Game).PlayCue("Explosions");

                        ((Game1)Game).ChangeGameState(
                            Game1.GameState.END, currentLevel);
                    }

                    E_shots.RemoveAt(j);
                    ((Game1)Game).PlayCue("Explosions");
                }//if collision
            }

            for (int j = 0; j < PowerUp.Count; ++j)
            {
                // Update each model
                if (player.CollidesWith(PowerUp[j].model, PowerUp[j].GetWorld()))
                {
                    PowerUp.RemoveAt(j);
                    ((Game1)Game).StartPowerUp(Game1.PowerUps.POWER_UP_FIRE);

                }
            }

            for (int j = 0; j < Health.Count; ++j)
            {
                // Update each model
                if (player.CollidesWith(Health[j].model, Health[j].GetWorld()))
                {
                    Health.RemoveAt(j);
                    ((Game1)Game).StartPowerUp(Game1.PowerUps.POWER_UP_HEALTH);
                    ((Game1)Game).life += 50;
                    
                    if (((Game1)Game).life >= 200)
                        ((Game1)Game).life = 200;

                }
            }
        }

        protected void UpdateShots()
        {
            // Loop through shots
            for (int i = 0; i < shots.Count; ++i)
            {
                // Update each shot
                shots[i].Update();

                // If shot is out of bounds, remove it from game
                if (shots[i].GetWorld().Translation.Z < shotMinZ)
                {
                    shots.RemoveAt(i);
                    --i;
                }
                else
                {
                    // If shot is still in play, check for collisions
                    for (int j = 0; j < models.Count; ++j)
                    {
                        if (shots[i].CollidesWith(models[j].model,
                            models[j].GetWorld()))
                        {
                                // Collision! add an explosion.
                                explosions.Add(new ParticleExplosion(GraphicsDevice,
                                    models[j].GetWorld().Translation,
                                    ((Game1)Game).rnd.Next(
                                        particleExplosionSettings.minLife,
                                        particleExplosionSettings.maxLife),
                                    ((Game1)Game).rnd.Next(
                                        particleExplosionSettings.minRoundTime,
                                        particleExplosionSettings.maxRoundTime),
                                    ((Game1)Game).rnd.Next(
                                        particleExplosionSettings.minParticlesPerRound,
                                        particleExplosionSettings.maxParticlesPerRound),
                                    ((Game1)Game).rnd.Next(
                                        particleExplosionSettings.minParticles,
                                        particleExplosionSettings.maxParticles),
                                    explosionColorsTexture, particleSettings,
                                    explosionEffect));

                                shots.RemoveAt(i);
                                --i;

                            // Update score
                            ((Game1)Game).AddPoints(pointsPerKill * (currentLevel + 1));
                            // Collision! Remove the ship and the shot.
                            models.RemoveAt(j);
                            
                            ++enemyKilled;
                            ++totalEnemyKilled;

                            if (enemyKilled >= levelInfoList[currentLevel].missesAllowed)
                            {
                               // Reset();
                                ++currentLevel;

                                if (currentLevel == 9)
                                {
                                    ((Game1)Game).ChangeGameState(Game1.GameState.WIN,currentLevel);
                                }
                               
                                else
                                ((Game1)Game).ChangeGameState(Game1.GameState.LEVEL_CHANGE,currentLevel);
                                
                                enemyKilled = 0;
                            }

                          
                            if (totalEnemyKilled % 7 == 0)
                            {
                                
                                SpawnPowerUp();
                            }
                           
                            if (totalEnemyKilled % 10 == 0)
                            {
                                SpawnHealth();
                            }

                            ((Game1)Game).PlayCue("Explosions");

                            break;
                        }
                    }
                }
            }
        }

        public void EnemyShot()
        {

            //if distance between enemy and player is less than threshold, let enemy fire at the player

            float threshold = 700f + currentLevel * 50;
            float shotSpeed = 10 + currentLevel*2;
           
                for (int i = 0; i < models.Count; ++i)
                {

                    float distance = MathHelper.Distance(models[i].GetWorld().Translation.Z, player.GetWorld().Translation.Z);

                    //get the X,Y values of player to shoot toward them, Z will be opposite to camera direction
                    float px = player.GetWorld().Translation.X;
                    float py = player.GetWorld().Translation.Y;

                    Vector3 S_direction = E_direction * shotSpeed;

                    Vector3 S_pos = models[i].GetWorld().Translation + new Vector3(0, 0, 2);
                   
                    if (distance <= threshold)
                    {
                        E_shots.Add(new SpinningEnemy(
                       Game.Content.Load<Model>(@"models\ball"), models[i].GetWorld().Translation, S_direction, 0, 0, 1));
                       
                        break;
                    }
                }
        }

        protected void UpdateEShots()
        {
            for (int i = 0; i < E_shots.Count; ++i)
            {
                // Update each shot
                E_shots[i].Update();

                if (E_shots[i].GetWorld().Translation.Z > player.GetWorld().Translation.Z + 100)
                {
                    E_shots.RemoveAt(i);
                    --i;
                }

            }

        }

        protected void UpdateExplosions(GameTime gameTime)
        {
            // Loop through and update explosions
            for (int i = 0; i < explosions.Count; ++i)
            {
                explosions[i].Update(gameTime);
                // If explosion is finished, remove it
                if (explosions[i].IsDead)
                {
                    explosions.RemoveAt(i);
                    --i;
                }
            }
        }
    }
}
